#####################################################################
# File:         ComponentContainer.py
# Product:      KnowledgeEngine
#
# Copyright:    Copyright (c) 2002-2004, Faulkner Technologies.
# Author:       Brian Oliver
#
# Description:  This file defines the abstract ComponentContainer class, the
#               base class of all component containers that are contained
#               with in Models and used for assessment.
#
# See Also:     GenericContainer, Component
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
import Common
from GenericContainer import GenericContainer, GenericContainerInfo
from Component import Component, ComponentInfo
from ComponentContext import ComponentContext
from DependantsManager import DependantsManager
from Report import *
from KERFProcessor import *
from PropertyDefinition import *
from PropertyTypes import *
from PropertyFormats import *
from PropertyStructures import *
from ReadableScore import ReadableScoreInfo, ReadableScore

#####################################################################
## Zope Library Imports
# (none)

#####################################################################
## Python Library Imports
# (none)

#####################################################################
## ComponentContainerInfo Class Definition
class ComponentContainerInfo(GenericContainerInfo, ComponentInfo, ReadableScoreInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """

    #---------------------------------------------------------------#

    def getClassName(self):
        """
            Returns the name of the class for which
            we are providing information.
        """
        return 'ComponentContainer'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """

        return "Component Container"

    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            ABSTRACT: Return the identity type.

            This is a short string used for prefixing object identities
        """

        return "componentcontainer"

    #---------------------------------------------------------------#

    def isInstanceOf(self, className):
        """
            Returns if the class is an instance of the specified
            className, or implements the interface specified
            by the className
        """

        return className == 'ComponentContainer' or ComponentInfo.isInstanceOf(self, className) or GenericContainerInfo.isInstanceOf(self, className) or ReadableScoreInfo.isInstanceOf(self, className)
        
    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in 
            the specified container (containerInstance)
            
            NOTE: This is from this class' instance perspective.
        """

        #DEFAULT RULE: ComponentContainers are permitted in where Components are permitted OR
        #              in containers that permit them (this last disjunction allows for other special containers (like Models)
        #              to control the ComponentContainers that may be children.
        return ComponentInfo.isPermittedInContainer(self, containerClassInfo, containerInstance, instance) or containerClassInfo.isChildPermitted(self)

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a
            child of the class' instances.

            NOTE: This is from this class' instance perspective.

            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """

        #DEFAULT RULE: ComponentContainers may have Component children
        return childClassInfo.isInstanceOf('Component')
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        GenericContainerInfo.onConfigureProperties(self)
        ComponentInfo.onConfigureProperties(self)
     
     #---------------------------------------------------------------#
 
    def onConfigureEvents(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific events using the EventReflection interface.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class events for reflection
        GenericContainerInfo.onConfigureEvents(self)

    #---------------------------------------------------------------#

#####################################################################
## ComponentContainer Class Definition
class ComponentContainer(GenericContainer, Component):
    """
        Base class for all Faulkner KnowledgeEngine Component Container
        Components
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = ComponentContainerInfo()

    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the container"

        GenericContainer.__init__(self, id, identity)
        Component.__init__(self, id, identity)

        #perform onAfterInstanciation processing
        self.onAfterInstanciation()

    #---------------------------------------------------------------#

    def __setstate__(self, state):
        """
            Initialise the state of the Object
            (does backward compatibility)
        """

        #perform default GenericContainer persistent actions
        GenericContainer.__setstate__(self, state)

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object
            has been loaded into memory.

            This is useful for performing backwards compatibility
        """

        #perform Component.onAfterInstanciation processing
        GenericContainer.onAfterInstanciation(self)

        ###print "DEBUG: ComponentContainer.onAfterInstanciation: Completed"

    #---------------------------------------------------------------#

    #the following component methods override
    #the GenericContainer methods
    getIdentity = Component.getIdentity
    getParent = Component.getParent
    getParents = Component.getParents
    onAfterClone = Component.onAfterClone

    #---------------------------------------------------------------#

    def onBeforeIdentityChange(self):
        """
            Handle Knowledge Engine specific actions before the
            identity of this object is changed.

            Called By GenericObject.onAfterClone
        """

        GenericContainer.onBeforeIdentityChange(self)
        Component.onBeforeIdentityChange(self)

    #---------------------------------------------------------------#

    def onAfterIdentityChange(self, identityMappings):
        """
            Handle Knowledge Engine specific actions after the
            identity of this object has changed.

            The identityMappings dictionary holds
            mappings from oldIdentities -> newIdentities
            (including this object)

        """

        #perform default identity changes
        Component.onAfterIdentityChange(self, identityMappings)

    #---------------------------------------------------------------#

    def onAfterAdd(self, componentContext = None):
        "Handle Knowledge Engine specific actions after this object has been added to a container"

        GenericContainer.onAfterAdd(self)
        Component.onAfterAdd(self, componentContext)

        ###print "DEBUG: ComponentContainer.onAfterAdd: Completed"

    #---------------------------------------------------------------#

    def onBeforeDelete(self, componentContext = None):
        "Handle Knowledge Engine specific actions before this object is deleted"

        GenericContainer.onBeforeDelete(self)
        Component.onBeforeDelete(self, componentContext)

        ###print "DEBUG: ComponentContainer.onBeforeDelete: Completed"

    #---------------------------------------------------------------#

    def getChildren(self):
        """
            Returns a sequence of the immediate child Components
            of this container, sorted by the Component.getOrder()
        """

        #get the immediate children of the container
        children = self.objectValues()

        #filter the Components only
        components = filter(lambda x : Common.isinstance(x, Component), children)

        #sort the component by Component.getOrder()
        components.sort(lambda x, y : -(x.getOrder() < y.getOrder()))

        #ensure if the container is ordered, that the children have unique positions
        if self.isOrderedContainer():
            lastChild = None
            for child in components:
                if lastChild == None:
                    lastChild = child
                else:
                    isAscending = lastChild.getOrder() < child.getOrder()
                    
                    if not isAscending:
                        child.setOrder(lastChild.getOrder() + 1)
                        
                    lastChild = child

        return components

    #---------------------------------------------------------------#

    def getDescendantsInContext(self, componentContext = None):
        """
            Returns the descendants of the container as a
            dictionary, indexed by GenericObject.getIdentity(componentContext)

            NOTE: takes component contexts into account!
        """

        #create the resulting dictionary (initially empty)
        lResult = {}

        #iterate over the child components in this container
        for lChild in self.objectValues():

            #is the child derived from Component?
            if isinstance(lChild, Component):

                #add the current child to the result
                lResult[Component.getIdentity(lChild, componentContext)] = lChild

                #is the current child composite?
                if isinstance(lChild, ComponentContainer):

                    #add the object descendants
                    lResult.update(lChild.getDescendantsInContext(componentContext))

        return lResult

    #---------------------------------------------------------------#

    def isPreviewPermitted(self):
        """
            Return if the component is allowed to be previewed.

            By default all components can be previewed, some however
            should not be previewed. eg: Options whose display is
            dependant on their parents.
        """

        return Component.isPreviewPermitted(self)

    #---------------------------------------------------------------#

    def isOrderedContainer(self):
        """
            Return if the children of the object may use ordering.

            This is to allow the 'Order' column in the Content
            management tab in Zope
        """

        return 1
    
    #---------------------------------------------------------------#
        
    def getScore(self, assessmentCache, componentContext = None):
        """
            Return the score of the Model with the
            specified assessment cache.
        """

        #return the sum of the child scores that implement readablescore AND are shown given the specified assessment cache        
        result = 0
        for child in self.getChildren():
            if child.isInstanceOf("ReadableScore") and child.isShown(assessmentCache, componentContext):
                if child.isInstanceOf("ComponentContainer"):
                    result += child.getScore(assessmentCache, componentContext)
                else:
                    result += child.getScore(assessmentCache, componentContext) * child.getWeighting()
               
                
        return result
    
    #---------------------------------------------------------------#
        
    def getMaxScore(self, assessmentCache, componentContext = None):
        """
            Return the MaxScore of the Model with the
            specified assessment cache.
        """

        #return the sum of the child MaxScores that implement readablescore AND are shown given the specified assessment cache        
        result = 0
        for child in self.getChildren():
            if child.isInstanceOf("ReadableScore") and child.isShown(assessmentCache, componentContext):
                if child.isInstanceOf("ComponentContainer"):
                    result += child.getMaxScore(assessmentCache, componentContext)
                else:
                    result += child.getMaxScore() * child.getWeighting()
                    
        return result
    
    #---------------------------------------------------------------#
    
    def getFirstComponentChild(self, assessmentCache = None):
        """
            Returns the first ordered child in the container.
            (optionally using the assessmentCache for
            dynamically controlled children)
            
            RETURNS: None if the aren't any ordered children or if the 
            container isn't ordered
        """
        
        #is the container ordered?
        if self.isOrderedContainer():
        
            #get the ordered children 
            siblings = [sibling for sibling 
                        in self.getChildren() 
                        if hasattr(sibling, 'isOrderable') and sibling.isOrderable()]
            
            #sort the siblings (in non-descending order)
            siblings.sort(lambda x, y : -(x.getOrder() < y.getOrder()))            

            #are there any siblings
            if len(siblings) == 0:            
                return None
            else:   
                return siblings[0]
                    
        else:
            return None         
    
    #---------------------------------------------------------------#

    def getComponentSiblingAfterChild(self, child, assessmentCache = None):
        """
            Returns the child immediately after the specified child
            (optionally using the assessmentCache for
            dynamically controlled children)
            
            RETURNS: None if the aren't any children after the specified
            child (or if the container isn't ordered)
        """
        
        #is the container ordered?
        if self.isOrderedContainer() and child.isOrderable():
                   
            #get the ordered children that occur after the specified child
            nextSiblings = [sibling for sibling 
                            in self.getChildren() 
                            if hasattr(sibling, 'isOrderable') and sibling.isOrderable() and sibling.getOrder() > child.getOrder()]
            
            #sort the previous siblings (in non-descending order so the next sibling is first)
            nextSiblings.sort(lambda x, y : -(x.getOrder() < y.getOrder()))            
            
            #the specified child is the first and only in the list
            if len(nextSiblings) == 0:            
                return None
            else:   
                return nextSiblings[0]
                    
        else:
            return None         
    
    #---------------------------------------------------------------#

    def getLastComponentChild(self, assessmentCache = None):
        """
            Returns the last ordered child in the container.
            (optionally using the assessmentCache for
            dynamically controlled children)
            
            RETURNS: None if the aren't any ordered children or if the 
            container isn't ordered
        """
        
        #is the container ordered?
        if self.isOrderedContainer():
        
            #get the ordered children
            siblings = [sibling for sibling 
                        in self.getChildren() 
                        if hasattr(sibling, 'isOrderable') and sibling.isOrderable()]
            
            #sort the siblings (in descending order so the previous sibling is first)
            siblings.sort(lambda x, y : -(x.getOrder() > y.getOrder()))            

            #are there any siblings
            if len(siblings) == 0:            
                return None
            else:   
                return siblings[0]
                    
        else:
            return None         
    
    #---------------------------------------------------------------#

    def getComponentSiblingBeforeChild(self, child, assessmentCache = None):
        """
            Returns the child immediately before the specified child
            (optionally using the assessmentCache for
            dynamically controlled children)
            
            RETURNS: None if the aren't any children before the specified
            child (or if the container isn't ordered)
        """
        
        #is the container ordered?
        if self.isOrderedContainer() and child.isOrderable():
        
            #get the ordered children that occur before the specified child
            previousSiblings = [sibling for sibling 
                                in self.getChildren() 
                                if hasattr(sibling, 'isOrderable') and sibling.isOrderable() and sibling.getOrder() < child.getOrder()]
            
            #sort the previous siblings (in descending order so the previous sibling is first)
            previousSiblings.sort(lambda x, y : -(x.getOrder() > y.getOrder()))            

            #the specified child is the first and only in the list
            if len(previousSiblings) == 0:            
                return None
            else:   
                return previousSiblings[0]
                    
        else:
            return None         
    
    #---------------------------------------------------------------#

    def getLastDescendantComponentChild(self, assessmentCache = None):
        """
            Returns the last most descendant of the container

            (optionally using the assessmentCache for
            dynamically controlled children)
            
            RETURNS: self if the aren't any children 
            (or if the container isn't ordered)
        """

        #get the last child
        lastChild = self.getLastComponentChild(assessmentCache)
        
        #is the last child None?
        if lastChild is None:
            return self
        elif lastChild.isInstanceOf("ComponentContainer"):
            return lastChild.getLastDescendantComponentChild(assessmentCache)
        else:
            return lastChild
            
    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation
# (none)
